package co.touchlab.skie.buildsetup.main.tasks

import co.touchlab.skie.buildsetup.util.version.SupportedKotlinVersion
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.io.File
import kotlin.io.path.createParentDirectories
import kotlin.io.path.writeText
import kotlin.math.min

abstract class GeneratePrimarySmokeTestsCIActionTask : DefaultTask() {

    @get:Input
    abstract val latestKotlinVersion: Property<SupportedKotlinVersion>

    @get:InputDirectory
    abstract val libraryTestResources: DirectoryProperty

    @get:OutputFile
    abstract val pushTriggerOutputPath: RegularFileProperty

    @get:OutputFile
    abstract val manualTriggerOutputPath: RegularFileProperty

    init {
        group = "other"
        description = "Generates the primary smoke-tests workflow file."
    }

    @TaskAction
    fun execute() {
        val kotlinVersionName = latestKotlinVersion.get().name.toString()
        val testResources = libraryTestResources.get().asFile

        val librariesTestBatches = LibrariesTestBatch.allFrom(kotlinVersionName, testResources)

        generatePushTriggerWorkflow(kotlinVersionName, librariesTestBatches)
        generateManualTriggerWorkflow(kotlinVersionName, librariesTestBatches)
    }

    private fun generatePushTriggerWorkflow(latestVersionName: String, librariesTestBatches: List<LibrariesTestBatch>) {
        val outputPath = this@GeneratePrimarySmokeTestsCIActionTask.pushTriggerOutputPath.get().asFile.toPath()

        outputPath.createParentDirectories()

        val workflow = getSmokeTestsPushTriggerWorkflow(latestVersionName, librariesTestBatches)

        outputPath.writeText(workflow)
    }

    private fun generateManualTriggerWorkflow(latestVersionName: String, librariesTestBatches: List<LibrariesTestBatch>) {
        val outputPath = this@GeneratePrimarySmokeTestsCIActionTask.manualTriggerOutputPath.get().asFile.toPath()

        outputPath.createParentDirectories()

        val workflow = getSmokeTestsManualTriggerWorkflow(latestVersionName, librariesTestBatches)

        outputPath.writeText(workflow)
    }

    private fun getSmokeTestsPushTriggerWorkflow(latestVersionName: String, librariesTestBatches: List<LibrariesTestBatch>): String = $$"""
        name: Smoke Tests

        # Do not edit - generated by `generateCIActions` Gradle task.

        on:
          push:
            branches:
              - '**'
          pull_request_target:
            branches:
              - main

        concurrency:
          group: ci-smoke-tests-${{ github.ref }}

    """.trimIndent() + getSmokeTestsBaseWorkflow(librariesTestBatches, latestVersionName)

    private fun getSmokeTestsManualTriggerWorkflow(latestVersionName: String, librariesTestBatches: List<LibrariesTestBatch>): String = """
        name: Smoke Tests (Manual)

        # Do not edit - generated by `generateCIActions` Gradle task.

        on:
          workflow_dispatch:
            inputs:
              suites:
                type: string
                required: false
                default: 'acceptance,type-mapping,gradle,${librariesTestBatches.joinToString(",") { "lib-${it.range}" }}'
                description: |
                  Comma-separated test suites to run. Available: acceptance, type-mapping, gradle, ${librariesTestBatches.joinToString(", ") { "lib-${it.range}" }}
                  Leave empty to run all. Use '!' prefix to exclude (e.g., '!lib-0-499').
              kotlin_version_name:
                type: string
                required: false
                description:
                  'The name of the supported Kotlin version (SKIE flavor) to test. Latest supported version is used for all tests if not specified.'
              compiler_version:
                type: string
                required: false
                description:
                  'The Kotlin compiler version used by the tests. Defaults to compiler version of the selected Kotlin version name if not specified. If specified, the Kotlin version name must be specified as well.'
              linkage:
                type: choice
                options:
                  - static
                  - dynamic
                required: true
                default: static
                description:
                  'The linkage mode to use for the tests. "static" will produce static frameworks, "dynamic" will produce dynamic frameworks.'
              configuration:
                type: choice
                options:
                  - debug
                  - release
                required: true
                default: debug
                description:
                  'The configuration to use for the tests. "release" will produce release builds, "debug" will produce debug builds (type mapping tests currently always use release).'
              target:
                type: choice
                options:
                  - ios_arm64
                  - ios_x64
                  - ios_simulator_arm64
                  - macos_arm64
                  - macos_x64
                required: true
                default: macos_arm64
                description:
                  'The target to use for the type mapping tests.'

        concurrency:
          group: ci-smoke-tests-manual
          cancel-in-progress: false

    """.trimIndent() + getSmokeTestsBaseWorkflow(
        librariesTestBatches = librariesTestBatches,
        versionName = $$"${{ inputs.kotlin_version_name && (inputs.compiler_version && format('{0}[{1}]', inputs.kotlin_version_name, inputs.compiler_version) || inputs.kotlin_version_name) || '$$latestVersionName' }}",
    )

    private fun getSmokeTestsBaseWorkflow(
        librariesTestBatches: List<LibrariesTestBatch>,
        versionName: String,
    ): String = $$"""

        permissions:
          contents: read
          checks: write

        jobs:
          acceptance-tests:
            name: Acceptance Tests ($$versionName)
            if: |-
              (!inputs.suites || (contains(inputs.suites, 'acceptance') && !contains(inputs.suites, '!acceptance')))
            runs-on: self-hosted
            steps:
              - name: Checkout Repo
                uses: actions/checkout@v3
                with:
                  submodules: true
                  token: ${{ secrets.ACCEPTANCE_TESTS_TOKEN }}
              - name: Prepare Worker
                uses: ./.github/actions/prepare-worker
              - name: Run Acceptance Tests
                uses: gradle/gradle-build-action@v2.4.2
                with:
                  arguments: ":acceptance-tests:functional:test -PversionSupport.kotlin.enabledVersions=$$versionName"
                  build-root-directory: SKIE
                env:
                  KOTLIN_LINK_MODE: ${{ inputs.linkage }}
                  KOTLIN_BUILD_CONFIGURATION: ${{ inputs.configuration }}
              # Log size can be too large which causes significant performance issues
              # - name: Publish Test Report
              #   uses: mikepenz/action-junit-report@v3
              #   if: ${{ failure() || success() }}
              #   with:
              #     check_name: "Smoke Test Reports - Functional Tests"
              #     report_paths: 'SKIE/acceptance-tests/build/test-results/functional__*/TEST-*.xml'
              #     require_tests: true

          gradle-tests:
            name: Gradle Tests ($$versionName)
            runs-on: self-hosted
            needs: [acceptance-tests]
            if: |
              !cancelled() &&
              (!inputs.suites || (contains(inputs.suites, 'gradle') && !contains(inputs.suites, '!gradle')))
            steps:
              - name: Checkout Repo
                uses: actions/checkout@v3
                with:
                  submodules: true
                  token: ${{ secrets.ACCEPTANCE_TESTS_TOKEN }}
              - name: Prepare Worker
                uses: ./.github/actions/prepare-worker
              - name: Run Gradle Tests
                uses: gradle/gradle-build-action@v2.4.2
                id: run-tests
        #      TODO The targets cannot be selected at the moment due to a mismatch in the name used by Gradle and other kinds of tests
        #      "-Pmatrix.targets=${{ inputs.target || 'macosArm64' }}"
                with:
                  arguments: >-
                    :test
                    -PtestLevel=smoke
                    -PtestType=gradle
                    "-Pmatrix.targets=macosArm64"
                    "-Pmatrix.configurations=${{ inputs.configuration || 'debug' }}"
                    "-Pmatrix.linkModes=${{ inputs.linkage || 'static' }}"
                    "-PversionSupport.kotlin.enabledVersions=$$versionName"
                  build-root-directory: test-runner
              - name: Publish Test Report
                uses: mikepenz/action-junit-report@v4
                if: ${{ failure() || success() }}
                with:
                  check_name: "Smoke Test Reports - Gradle Tests"
                  report_paths: 'test-runner/build/test-results/test/TEST-*.xml'
                  require_tests: true

          type-mapping-tests:
            name: Type Mapping Tests ($$versionName)
            runs-on: self-hosted
            needs: [gradle-tests]
            if: |
              !cancelled() &&
              (!inputs.suites || (contains(inputs.suites, 'type-mapping') && !contains(inputs.suites, '!type-mapping')))
            steps:
              - name: Checkout Repo
                uses: actions/checkout@v3
                with:
                  submodules: true
                  token: ${{ secrets.ACCEPTANCE_TESTS_TOKEN }}
              - name: Prepare Worker
                uses: ./.github/actions/prepare-worker
              - name: Run Type Mapping Tests
                uses: gradle/gradle-build-action@v2.4.2
                id: run-tests
                with:
                  arguments: ":acceptance-tests:type-mapping:test -PversionSupport.kotlin.enabledVersions=$$versionName"
                  build-root-directory: SKIE
                env:
                  KOTLIN_LINK_MODE: ${{ inputs.linkage }}
                  KOTLIN_TARGET: ${{ inputs.target }}
                  KOTLIN_BUILD_CONFIGURATION: ${{ inputs.configuration }}
              # Log size can be too large which causes significant performance issues
              # - name: Publish Test Report
              #   uses: mikepenz/action-junit-report@v3
              #   if: ${{ (failure() || success()) && steps.run-tests.outcome != 'skipped' }}
              #   with:
              #     check_name: "Smoke Test Reports - Type Mapping Tests"
              #     report_paths: 'SKIE/acceptance-tests/build/test-results/type-mapping__*/TEST-*.xml'
              #     require_tests: true

        """.trimIndent() +
        librariesTestBatches.joinToString(System.lineSeparator()) {
            getExternalLibrariesJob(it, versionName).prependIndent("  ")
        } + "\n"

    private fun getExternalLibrariesJob(
        testBatch: LibrariesTestBatch,
        versionName: String,
    ): String = $$"""

          external-libraries-tests-$${testBatch.range}:
            name: External Libraries Tests ($$versionName) $${testBatch.range}
            needs: [type-mapping-tests]
            if: |
              !cancelled() &&
              (!inputs.suites || (contains(inputs.suites, 'lib-$${testBatch.range}') && !contains(inputs.suites, '!lib-$${testBatch.range}')))
            runs-on: self-hosted
            steps:
              - name: Checkout Repo
                uses: actions/checkout@v3
                with:
                  submodules: true
                  token: ${{ secrets.ACCEPTANCE_TESTS_TOKEN }}
              - name: Prepare Worker
                uses: ./.github/actions/prepare-worker
              - name: Run External Libraries Tests
                uses: gradle/gradle-build-action@v2.4.2
                with:
                  arguments: ":acceptance-tests:libraries:test -PversionSupport.kotlin.enabledVersions=$$versionName"
                  build-root-directory: SKIE
                env:
                  KOTLIN_LINK_MODE: ${{ inputs.linkage }}
                  KOTLIN_BUILD_CONFIGURATION: ${{ inputs.configuration }}
                  onlyIndices: "$${testBatch.range}"
              # Log size can be too large which causes significant performance issues
              # - name: Publish Test Report
              #   uses: mikepenz/action-junit-report@v3
              #   if: ${{ failure() || success() }}
              #   with:
              #     check_name: "Smoke Test Reports - External Libraries Tests"
              #     report_paths: 'SKIE/acceptance-tests/build/test-results/libraries__*/TEST-*.xml'
              #     require_tests: true
        """.trimIndent()

    data class LibrariesTestBatch(
        val range: String,
    ) {

        companion object {

            const val libraryTestsBatchSize: Int = 500

            fun allFrom(kotlinVersionName: String, libraryTestResources: File): List<LibrariesTestBatch> {
                val numberOfLibraryTests = getNumberOfLibraryTests(kotlinVersionName, libraryTestResources)

                return (0..(numberOfLibraryTests / libraryTestsBatchSize)).map {
                    from(it, numberOfLibraryTests)
                }
            }

            private fun from(index: Int, numberOfLibraryTests: Int): LibrariesTestBatch {
                val range = "${index * libraryTestsBatchSize}-${min((index + 1) * libraryTestsBatchSize - 1, numberOfLibraryTests - 1)}"

                return LibrariesTestBatch(range)
            }

            private fun getNumberOfLibraryTests(kotlinVersionName: String, libraryTestResources: File): Int =
                libraryTestResources.resolve(kotlinVersionName)
                    .resolve("libraries.lock")
                    .takeIf { it.exists() }
                    ?.readLines()
                    ?.count { it.trim().startsWith("\"index\": ") }
                    ?: 0
        }
    }
}
